#include "GenAlg.h"

GenAlg::GenAlg(	int		iPopSize,
                double	dMutRat,
                double	dCrossRat,
                int		iNumWeights) :
m_iPopSize(iPopSize),
m_dMutationRate(dMutRat),
m_dCrossoverRate(dCrossRat),
m_iChromoLength(iNumWeights),
m_dTotalFitness(0),
m_iGeneration(0),
m_iFittestGenome(0),
m_dBestFitness(0),
m_dWorstFitness(99999999),
m_dAverageFitness(0)
{
	// initialise population with chromosomes consisting of random
	// weights and all fitnesses set to zero
	for (int i=0; i<m_iPopSize; ++i)
	{
		m_vecPop.push_back(Genome());

		for (int j=0; j<m_iChromoLength; ++j)
		{
			m_vecPop[i].vecWeights.push_back(RandomClamped());
		}
	}
}

/*-------------------------------------------------------------------------------------------------------------*/
void GenAlg::Mutate(vector<double> &chromo)
{
	// traverse the chromosome and mutate each weight dependent
	// on the mutation rate
	for (int i=0; i<chromo.size(); ++i)
	{
		// do we perturb this weight?
		if (RandFloat() < m_dMutationRate)
		{
			// add or subtract a small value to the weight
			chromo[i] += (RandomClamped() * D_MAX_PERTURBATION);
		}
	}
}

/*-------------------------------------------------------------------------------------------------------------*/
Genome GenAlg::GetChromoRoulette()
{
	// generate a random number between 0 & total fitness count
	double Slice = (double)(RandFloat() * m_dTotalFitness);

	// this will be set to the chosen chromosome
	Genome TheChosenOne;
	
	// go through the chromosones adding up the fitness so far
	double FitnessSoFar = 0;
	
	for (int i=0; i<m_iPopSize; ++i)
	{
		FitnessSoFar += m_vecPop[i].dFitness;
		
		// if the fitness so far > random number return the chromo at 
		// this point
		if (FitnessSoFar >= Slice)
		{
			TheChosenOne = m_vecPop[i];
			break;
		}	
	}

	return TheChosenOne;
}

/*-------------------------------------------------------------------------------------------------------------*/
void GenAlg::Crossover(	const vector<double> &mum,
                        const vector<double> &dad,
                        vector<double>       &baby1,
                        vector<double>       &baby2)
{
	// just return parents as offspring dependent on the rate
	// or if parents are the same
	if ( (RandFloat() > m_dCrossoverRate) || (mum == dad)) 
	{
		baby1 = mum;
		baby2 = dad;

		return;
	}

	// determine a crossover point
	int cp = RandInt(0, m_iChromoLength - 1);

	// create the offspring
	for (int i=0; i<cp; ++i)
	{
		baby1.push_back(mum[i]);
		baby2.push_back(dad[i]);
	}

	for (int i=cp; i<mum.size(); ++i)
	{
		baby1.push_back(dad[i]);
		baby2.push_back(mum[i]);
	}
	
	return;
}

/*-------------------------------------------------------------------------------------------------------------*/
vector<Genome> GenAlg::Epoch(vector<Genome> &old_Pop)
{
	// assign the given population to the classes population
	m_vecPop = old_Pop;

	// reset the appropriate variables
	Reset();

	// sort the population (for scaling and elitism)
	sort(m_vecPop.begin(), m_vecPop.end());

	// calculate best, worst, average and total fitness
	CalculateBestWorstAvTot();

	// create a temporary vector to store new chromosomes
	vector <Genome> vecNewPop;

	// Now to add a little elitism we shall add in some copies of the
	// fittest genomes. Make sure we add an EVEN number or the roulette

	// wheel sampling will crash
	if (!(I_NUM_COPIES_ELITE * I_NUM_ELITE % 2))
	{
		GrabBest(I_NUM_ELITE, I_NUM_COPIES_ELITE, vecNewPop);
	}
	
	// now we enter the GA loop
	
	// repeat until a new population is generated
	while (vecNewPop.size() < m_iPopSize)
	{
		//grab two chromosomes
		Genome mum = GetChromoRoulette();
		Genome dad = GetChromoRoulette();

		// create some offspring via crossover
		vector<double>		baby1, baby2;

		Crossover(mum.vecWeights, dad.vecWeights, baby1, baby2);

		// now we mutate
		Mutate(baby1);
		Mutate(baby2);

		// now copy into vecNewPop population
		vecNewPop.push_back(Genome(baby1, 0));
		vecNewPop.push_back(Genome(baby2, 0));
	}

	// finished so assign new pop back into m_vecPop
	m_vecPop = vecNewPop;

	return m_vecPop;
}

/*-------------------------------------------------------------------------------------------------------------*/
void GenAlg::GrabBest(	int				iBest,
				const int		iNumCopies,
				vector<Genome> &vecPop)
{
	// add the required amount of copies of the n most fittest 
	// to the supplied vector
	while(iBest--)
	{
		for (int i=0; i<iNumCopies; ++i)
		{
			vecPop.push_back(m_vecPop[(m_iPopSize - 1) - iBest]);
	  }
	}
}

/*-------------------------------------------------------------------------------------------------------------*/
void GenAlg::CalculateBestWorstAvTot()
{
	m_dTotalFitness = 0;
	
	double dHighestSoFar = 0;
	double dLowestSoFar  = 9999999;
	
	for (int i=0; i<m_iPopSize; ++i)
	{
		// update fittest if necessary
		if (m_vecPop[i].dFitness > dHighestSoFar)
		{
			dHighestSoFar	 = m_vecPop[i].dFitness;
			
			m_iFittestGenome = i;

			m_dBestFitness	 = dHighestSoFar;
		}
		
		// update worst if necessary
		if (m_vecPop[i].dFitness < dLowestSoFar)
		{
			dLowestSoFar = m_vecPop[i].dFitness;
			
			m_dWorstFitness = dLowestSoFar;
		}
		
		m_dTotalFitness	+= m_vecPop[i].dFitness;
		
		
	}
	
	// next chromo
	
	m_dAverageFitness = m_dTotalFitness / m_iPopSize;
}

/*-------------------------------------------------------------------------------------------------------------*/
void GenAlg::Reset()
{
	m_dTotalFitness		= 0;
	m_dBestFitness		= 0;
	m_dWorstFitness		= 9999999;
	m_dAverageFitness	= 0;
}
